﻿#region Copyright (c) 2008 plexBB Team

/*  Copyright (c) 2008 plexBB Team
 *  
 *  This file is free software: you may copy, redistribute and/or modify it  
 *  under the terms of the GNU General Public License as published by the  
 *  Free Software Foundation, either version 2 of the License, or (at your  
 *  option) any later version.  
 *  
 *  This file is distributed in the hope that it will be useful, but  
 *  WITHOUT ANY WARRANTY; without even the implied warranty of  
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  
 *  General Public License for more details.  
 *  
 *  You should have received a copy of the GNU General Public License  
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Text;
using System.Web;
using System.Web.UI;
using PlexBB.Controls;
using PlexBB.Controls.Admin;
using PlexBB.Data;
using PlexBB.Install;

namespace PlexBB
{
    [ToolboxData(@"<{0}:PlexBB runat=""server""></{0}:PlexBB>")]
    public class PlexBB : Control
    {
        private const string _httpContextKey = "PlexBB";
        private const string AdminControlsLocation = "Themes/Admin";
        private const string AdminControlsPrefix = "PlexBB.Controls.Admin.";
        private const string ControlsLocation = "Themes/Prosilver9";
        private const string ControlsPrefix = "PlexBB.Controls.";
        private static readonly object _installLock = new object();
        private static bool _installed;

        public PlexBBConfigurationSection Configuration { get; private set; }
        public PlexBBData Data { get; private set; }
        public User User { get; private set; }
        public PlexBBActionData RequestAction { get; private set; }
        public Control ActionControl { get; private set; }

        public Module CurrentModule
        {
            get
            {
                IModuleContainer moduleContainer = ActionControl as IModuleContainer;
                return moduleContainer != null ? moduleContainer.CurrentModule : null;
            }
        }

        public static PlexBB Current
        {
            get { return (PlexBB)HttpContext.Current.Items[_httpContextKey]; }
        }

        protected override void OnInit(EventArgs e)
        {
            Context.Items[_httpContextKey] = this;

            // initialize
            Configuration = (PlexBBConfigurationSection)ConfigurationManager.GetSection("plexBB");

            // install if required
            // using double check pattern
            if (!_installed)
            {
                lock (_installLock)
                {
                    if (!_installed)
                    {
                        PlexBBInstaller installer = new PlexBBInstaller(Configuration);
                        installer.InstallIfRequired();
                        _installed = true;
                    }
                }
            }

            Data = new PlexBBData(Configuration.ConnectionString, Configuration.DatabaseTablePrefix);
            // create RequestAction
            {
                Dictionary<string, string> currentParams = new Dictionary<string, string>();
                string prefix = Configuration.UrlParamPrefix;
                foreach (string key in Context.Request.QueryString)
                {
                    if (key != null && (prefix.Length == 0 || key.StartsWith(prefix)))
                    {
                        currentParams.Add(key.Substring(prefix.Length), Context.Request.QueryString[key]);
                    }
                }

                RequestAction = new PlexBBActionData(currentParams);
            }

            // get actionInfo
            PlexBBActionInfo actionInfo;
            if (!Actions.TryGetValue(RequestAction.Name ?? "index", out actionInfo))
            {
                throw new NotSupportedException("Not supported action type.");
            }

            // here will go authorization
            User = new User();

            // create and add control
            ActionControl = CreateForumControl(actionInfo.ControlType);
            if (!actionInfo.IsRawView)
            {
                Overall overall = CreateForumControl<Overall>();
                overall.ContentControl = ActionControl;
                Controls.Add(overall);
            }
            else
            {
                Controls.Add(ActionControl);
            }

            base.OnInit(e);
        }

        public string GetResourceServerPath(string relativeResourcePath)
        {
            return Path.Combine(Context.Server.MapPath(Configuration.BaseDir), relativeResourcePath);
        }

        public override void Dispose()
        {
            // Data can be null if PlexBB wasn't initialized
            if (Data != null)
            {
                Data.Dispose();
            }
            base.Dispose();
        }

        public string ResolveThemeUrl(string url)
        {
            return ResolveBaseDirUrl((ActionControl is Acp ? "Themes/Admin/" : "Themes/Prosilver9/") + url);
        }

        public string ResolveBaseDirUrl(string url)
        {
            return ResolveUrl(Configuration.BaseDir.TrimEnd('/') + "/" + url);
        }

        #region CreateForumControl

        public Control CreateForumControl(Type type)
        {
            // load ASCX file is control type is abstract
            // else just create control instance
            if (type.IsAbstract)
            {
                string prefix;
                string location;
                if (type.FullName.StartsWith(AdminControlsPrefix))
                {
                    prefix = AdminControlsPrefix;
                    location = AdminControlsLocation;
                }
                else
                {
                    prefix = ControlsPrefix;
                    location = ControlsLocation;
                }
                string controlPath = string.Format("{0}/{1}/{2}.ascx",
                                                   Configuration.BaseDir.TrimEnd('/'),
                                                   location,
                                                   type.FullName.Substring(prefix.Length).Replace('.', '/'));
                return Page.LoadControl(controlPath);
            }

            return (Control)Activator.CreateInstance(type);
        }

        public T CreateForumControl<T>() where T : ForumControl
        {
            return (T)CreateForumControl(typeof(T));
        }

        #endregion

        #region Actions

        private static Dictionary<string, PlexBBActionInfo> _actions;

        /// <summary>
        /// Gets collection of all PlexBB action controls.
        /// This collection is an extensibility point and  can be extended outside of PlexBB code.
        /// </summary>
        public static Dictionary<string, PlexBBActionInfo> Actions
        {
            get
            {
                if (_actions == null)
                {
                    _actions = new Dictionary<string, PlexBBActionInfo>
                                   {
                                       { "index", new PlexBBActionInfo(typeof(ForumListBody)) },
                                       { "register", new PlexBBActionInfo(typeof(Register)) },
                                       { "acp", new PlexBBActionInfo(typeof(Acp)) { IsRawView = true } },
                                       { "captcha", new PlexBBActionInfo(typeof(Captcha)) { IsRawView = true } }
                                   };
                }
                return _actions;
            }
        }

        public class PlexBBActionInfo
        {
            public PlexBBActionInfo(Type controlType)
            {
                ControlType = controlType;
            }

            public Type ControlType { get; set; }
            public bool IsRawView { get; set; }
        }

        #endregion

        #region ResolveActionUrl

        public string ResolveActionUrl(string action)
        {
            return ResolveActionUrl(action, (IDictionary<string, object>)null);
        }

        public string ResolveActionUrl(string action, string param1Name, object param1Value)
        {
            return ResolveActionUrl(action, new Dictionary<string, object> { { param1Name, param1Value } });
        }

        public string ResolveActionUrl(string action, string param1Name, object param1Value, string param2Name, object parame2Value)
        {
            return ResolveActionUrl(action, new Dictionary<string, object> { { param1Name, param1Value }, { param2Name, parame2Value } });
        }

        public string ResolveActionUrl(string action, string param1Name, object param1Value, string param2Name, object parame2Value, string param3Name,
                                       object param3Value)
        {
            return ResolveActionUrl(action,
                                    new Dictionary<string, object>
                                        { { param1Name, param1Value }, { param2Name, parame2Value }, { param3Name, param3Value } });
        }

        public string ResolveActionUrl(string action, params object[] parameters)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            for (int i = 0; i < parameters.Length; i += 2)
            {
                string key = (string)parameters[i];
                object value = parameters[i + 1];
                if (key != null)
                {
                    dictionary.Add(key, value);
                }
            }
            return ResolveActionUrl(action, dictionary);
        }

        public string ResolveActionUrl(string action, IDictionary<string, object> parameters)
        {
            // do not allow null or empty action
            if (action == null)
            {
                throw new ArgumentNullException("action");
            }
            if (action.Length == 0)
            {
                throw new ArgumentException("action");
            }

            HttpServerUtility server = Context.Server;
            string encodedPrefix = server.UrlEncode(Configuration.UrlParamPrefix);
            string forumUrlResolved;

            // we will not throw error that action does not exist;
            // just let the action to be resolved
            PlexBBActionInfo actionInfo;
            if (Actions.TryGetValue(action, out actionInfo) && actionInfo.IsRawView)
            {
                forumUrlResolved = Page.ResolveUrl("./Raw.aspx");
            }
            else
            {
                forumUrlResolved = Page.ResolveUrl(Configuration.ForumUrl.Length == 0 ? "./" : Configuration.ForumUrl);
            }

            StringBuilder url = new StringBuilder(forumUrlResolved);
            if (forumUrlResolved.IndexOf('?') == -1)
            {
                url.Append('?');
            }
            char lastChar = url[url.Length - 1];
            if (lastChar != '&' && lastChar != '?')
            {
                url.Append('&');
            }

            url.Append(encodedPrefix);
            url.Append("act=");
            url.Append(server.UrlEncode(action));

            if (parameters != null)
            {
                foreach (KeyValuePair<string, object> pair in parameters)
                {
                    if (pair.Value != null)
                    {
                        url.Append('&');
                        url.Append(encodedPrefix);
                        url.Append(server.UrlEncode(pair.Key));
                        url.Append('=');
                        url.Append(server.UrlEncode(Convert.ToString(pair.Value)));
                    }
                }
            }
            return url.ToString();
        }

        #endregion
    }
}

#region Old

//AuthenticateRequestEventArgs args = new AuthenticateRequestEventArgs();
//if (Context.User.Identity.IsAuthenticated)
//{
//    args.IsAuthenticated = true;
//    args.Username = Context.User.Identity.Name;
//}
//OnAuthenticateRequest(args);

//if (args.IsAuthenticated)
//{
//    // now we have username
//    _user = Data.Users.SingleOrDefault(u => u.Name.ToLower() == args.Username.ToLower());
//    if (_user == null)
//    {
//        // create dbuser with the same name, etc.
//        _user = new User() { Name = args.Username };
//        Data.Users.InsertOnSubmit(_user);
//        UserEventArgs userArgs = new UserEventArgs(_user);
//        OnCompleteRegisteredUser(userArgs);
//    }
//}
//else
//{
//    _user = Data.Users.Single(u => u.Name == "anonymous");
//}
//_user.UpdateLastVisit();

////
//// Add/Update current ForumSession
////
//string sessionCookieKey = Config.DatabaseTablePrefix + "_plexbb_session";
//string sessionID = null;

//string host = Context.Request.UserHostAddress;

//// get the cookie
//if (Context.Request.Cookies[sessionCookieKey] != null)
//{
//    // try to parse cookie value
//    try
//    {
//        sessionID = new Guid(Context.Request.Cookies[sessionCookieKey].Value).ToString("N");
//    }
//    catch (Exception) { }
//}

//ForumSession session;
//if (sessionID == null)
//{
//    // TODO: get bots' and guests' session by host + userid;

//    sessionID = Guid.NewGuid().ToString("N");
//    session = null;
//}
//else
//{
//    session = Data.ForumSessions.SingleOrDefault(s => s.SessionID == sessionID);
//}

//if (session == null)
//{
//    session = new ForumSession() { SessionID = sessionID };
//    Data.ForumSessions.InsertOnSubmit(session);
//}
//session.User = _user;
//session.Host = host;
//// TODO: also update current action, user agent, etc.

//Context.Response.Cookies.Add(new HttpCookie(sessionCookieKey, sessionID) { Expires = DateTime.Now.AddYears(2) });
////StringWriter sw = new StringWriter();
////Data.Log = sw;
////try
////{
//Data.SubmitChanges();
////}
////finally
////{
////    string s = sw.ToString();
////}


//// perform action
//CurrentAction.Init();


//public User User
//{
//    get { return _user; }
//}

//protected virtual void OnAuthenticateRequest(AuthenticateRequestEventArgs e)
//{
//    if (AuthenticateRequest != null)
//    {
//        AuthenticateRequest(this, e);
//    }
//}

//public event AuthenticateRequestEventHandler AuthenticateRequest;
//public event UserEventHandler CompleteRegisteredUser;

//protected virtual void OnCompleteRegisteredUser(UserEventArgs e)
//{
//    if (CompleteRegisteredUser != null)
//    {
//        CompleteRegisteredUser(this, e);
//    }
//}

//public virtual PlexBBData Data
//{
//    get
//    {
//        return PlexBBData.Current;
//    }
//}


//public delegate void AuthenticateRequestEventHandler(object sender, AuthenticateRequestEventArgs e);
//public delegate void UserEventHandler(object sender, UserEventArgs e);
//public class AuthenticateRequestEventArgs : EventArgs
//{
//    private string _username;
//    private bool _isAuthenticated;
//    public string Username
//    {
//        get { return _username; }
//        set { _username = value; }
//    }

//    public bool IsAuthenticated
//    {
//        get { return _isAuthenticated; }
//        set { _isAuthenticated = value; }
//    }
//}

//public class UserEventArgs : EventArgs
//{
//    private readonly User _user;
//    public UserEventArgs(User user)
//    {
//        _user = user;
//    }

//    public User User
//    {
//        get { return _user; }
//    }
//}

//static PlexBB()
//{

//    // gather info about PlexBB actions across all loaded assemblies
//    // this is a good extensebility point of PlexBB:
//    // a new template can be dropped into templates, a new action control created for it, 
//    // and registered as action using assembly attribute
//    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
//    {
//        Attribute[] attributes = Attribute.GetCustomAttributes(assembly, typeof(PlexBBActionAttribute));
//        foreach (PlexBBActionAttribute attribute in attributes)
//        {
//            _actions.Add(attribute.ActionID, new PlexBBActionInfo(attribute.ControlType, attribute.IsRawView));
//        }
//    }
//}

#endregion